﻿namespace SeedObject
{
    using BinaryFormatter = System.Runtime.Serialization.Formatters.Binary.BinaryFormatter;
    using CacheItemPriority = System.Web.Caching.CacheItemPriority;
    using CredentialCache = System.Net.CredentialCache;
    using DateTime = System.DateTime;
    using Encoding = System.Text.Encoding;
    using File = System.IO.File;
    using FileAccess = System.IO.FileAccess;
    using FileMode = System.IO.FileMode;
    using FileShare = System.IO.FileShare;
    using FileStream = System.IO.FileStream;
    using HttpContext = System.Web.HttpContext;
    using HttpRuntime = System.Web.HttpRuntime;
    using MemoryStream = System.IO.MemoryStream;
    using Path = System.IO.Path;
    using Stream = System.IO.Stream;
    using WebRequest = System.Net.WebRequest;
    using WebResponse = System.Net.WebResponse;
    using XmlSerializer = System.Xml.Serialization.XmlSerializer;
    using XmlWriter = System.Xml.XmlWriter;
    using XmlWriterSettings = System.Xml.XmlWriterSettings;

    /// <summary>
    /// Allows creation of objects from an XML definition, 
    /// creating the binary cache and hosts the object in the HttpRuntime.Cache
    /// </summary>
    public static class Builder
    {
        #region Fields
            // Controls the operations of writing and reading files in the concurrence environment
            private static System.Collections.Generic.IList<string> OperationsFiles 
                = new System.Collections.Generic.List<string>();
            // Addressing system and storage cache
            private static System.Collections.Generic.IDictionary<string, System.Collections.Generic.IList<ISeed>> Cache 
                = CreateCache();
        #endregion
        #region Properties
			private static bool _IndentedSerialization;
			/// <summary>
            /// Indented serialization Indicator
			/// </summary>  
            public static bool IndentedSerialization
			{
                get
                {
                    return _IndentedSerialization;
                }
                set 
                { 
                    _IndentedSerialization = value; 
                }
			}            
        #endregion
        #region Create
            /// <summary>
            /// Creates or returns instances of the requested
            /// </summary>
            /// <typeparam name="T">DBType of object to create</typeparam>
            /// <param name="xml">xml file</param>
            /// <returns>The requested object instance</returns>
            public static T Create<T>(string xml)
                where T : ISeed
            {
                // Build file address
                string Uri = string.Concat(Utility.XmlCache, xml);
                // Build the md5
                string KeyCache = Utility.MD5(xml);
                // Build the binary cache path
                string BinaryCache = Path.Combine(Utility.BinCache, KeyCache);
#if DesktopApplication
                // Check if the binary cache exists 
                if (File.Exists(BinaryCache))
                    return BinDes<T>(BinaryCache);
                
                // Xml deserialize from the image
                return BinSer(XmlDes<T>(Uri), BinaryCache);
#else
                // Exists in the runtime cache?
                if (Cache.ContainsKey(KeyCache))
                {
                    // Directs the seed pool
                    System.Collections.Generic.IList<ISeed> Seeds = Cache[KeyCache];
                    foreach (T seed in Seeds)
                        if (Seed.Synchronize(seed))
                            return ReportRestoration(seed);
                }

                // Check if the binary cache exists 
                if (File.Exists(BinaryCache))
                    return ReportRestoration(CreateCache(BinDes<T>(BinaryCache), KeyCache));

                // Xml deserialize from the image
                return ReportRestoration(BinSer(CreateCache(XmlDes<T>(Uri), KeyCache), BinaryCache));
#endif
            }
        #endregion
        #region CreateCache
            // Create the initial structure for the storage of runtime cache
            private static System.Collections.Generic.IDictionary<string, System.Collections.Generic.IList<ISeed>> 
                CreateCache()
            {
                // access the memory system
                object oCache = HttpRuntime.Cache.Get(Utility.ConcurrentObjectKey);
                if (oCache != null)
                    // access the object if you stay
                    Cache = oCache as System.Collections.Generic.IDictionary<string, System.Collections.Generic.IList<ISeed>>;
                else
                {
                    // creates the variable does not exist
                    Cache = new System.Collections.Generic.Dictionary<string, System.Collections.Generic.IList<ISeed>>();
                    // registers the object in the cache
                    HttpRuntime.Cache.Insert(Utility.ConcurrentObjectKey, Cache, null,
                                          DateTime.Now.AddHours(12),
                                          System.Web.Caching.Cache.NoSlidingExpiration,
                                          CacheItemPriority.NotRemovable,
                                          null);
                }
                return Cache;
            }
            // Hosts the object in the cache system
            private static T CreateCache<T>(T obj, string key)
                where T : ISeed
            {
                // Insert the object in the cache
                HttpRuntime.Cache.Insert(key, obj, null,
                                      DateTime.Now.AddHours(12),
                                      System.Web.Caching.Cache.NoSlidingExpiration,
                                      CacheItemPriority.NotRemovable,
                                      null);
                // If the object is not there insert in the structure
                lock (((System.Collections.ICollection)Cache).SyncRoot)
                {
                    if (!Cache.ContainsKey(key))
                        Cache.Add(key, new System.Collections.Generic.List<ISeed>());
                }
                System.Collections.Generic.IList<ISeed> ObjectsConcurrent = Cache[key];
                lock (((System.Collections.ICollection)ObjectsConcurrent).SyncRoot)
                {
                    if (!ObjectsConcurrent.Contains(obj))
                        ObjectsConcurrent.Add(obj);
                }
                return obj;
            }
        #endregion
        #region BinDes
            // Construct the object from binary definition
            private static T BinDes<T>(string file)
            {
                T instance;
                BinaryFormatter Serializer = new BinaryFormatter();

                //wait while you finish creating the file
                while (OperationsFiles.Contains(file))
                    System.Threading.Thread.Sleep(10);
                // Deserialize
                using (Stream Stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    instance = (T)Serializer.Deserialize(Stream);
                }
                return instance;
            }
        #endregion
        #region BinSer
            // Build the binary image from the instance of an object
            private static T BinSer<T>(T instance, string file)
            {
                BinaryFormatter Serializer = new BinaryFormatter();
                lock (((System.Collections.ICollection)OperationsFiles).SyncRoot)
                {
                    // Syncs files concurrent
                    if (!OperationsFiles.Contains(file))
                    {
                        using (Stream Stream =
                            new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))
                        {
                            OperationsFiles.Add(file);
                            Serializer.Serialize(Stream, instance);
                            OperationsFiles.Remove(file);
                        }
                    }
                }
                return instance;
            }
        #endregion   
        #region ReportRestoration
            // Prepares and informs the Handler object as it should restore
            private static T ReportRestoration<T>(T instance)
                 where T : ISeed
            {
                System.Collections.Generic.IList<ISeed> Seeds;

                instance.State |= State.Busy;

                // obtiene el codigo del programa en ejecucion
                string HashCode = HttpContext.Current.Handler.GetHashCode().ToString(Utility.CultureInfo);
                Seeds = HttpContext.Current.Session[HashCode] as System.Collections.Generic.IList<ISeed>;
                if (Seeds == null)
                {
                    Seeds = new System.Collections.Generic.List<ISeed>();
                    HttpContext.Current.Session[HashCode] = Seeds;
                }
                Seeds.Add(instance);
                return instance;
            }
        #endregion  
        #region ServerResponse
            // Provides web request
            private static WebResponse ServerResponse(string uri)
            {
                // set the petition for uri
                WebRequest WebRequest = WebRequest.Create(uri);

                // if required by the server, set the default credentials
                WebRequest.Credentials = CredentialCache.DefaultCredentials;

                // Create the server
                WebResponse Server = WebRequest.GetResponse();
                
                return Server;
            }
        #endregion
        #region Serialize
            /// <summary>
            /// Serializes an object graph
            /// </summary>
            /// <typeparam name="T">DBType of the object</typeparam>
            /// <param name="instance">Instance of the object</param>
            /// <returns>text with serializations</returns>
            public static string Serialize<T>(T instance)
            {
                // Encoding and set Indent
                XmlWriterSettings Settings = new XmlWriterSettings();
                Settings.Indent = _IndentedSerialization;
                Settings.Encoding = Encoding.UTF8;

                using (MemoryStream MemoryStream = new MemoryStream())
                {
                    // Create a writer
                    using (XmlWriter Writer = XmlWriter.Create(MemoryStream, Settings))
                    {
                        // create a XmlSerializer
                        XmlSerializer XmlSerializer = new XmlSerializer(instance.GetType());

                        // serialize in memory
                        XmlSerializer.Serialize(Writer, instance);
                    }

                    // returns a string encoded in UTF8 and eliminates strange character initial
                    return Encoding.UTF8.GetString(MemoryStream.ToArray()).Substring(1);
                }
            }
        #endregion
        #region XmlDes
            // create an object from xml image 
            private static T XmlDes<T>(string uri)
            {
                // Get the definition of the object 
                using (WebResponse Response = ServerResponse(uri))
                {
                    // create the XmlSerializer
                    XmlSerializer Serializer = new XmlSerializer(typeof(T));

                    // deserialize the object creating the instance
                    T Instance = (T)Serializer.Deserialize(Response.GetResponseStream());

                    // invokes the event after creating the object instance
                    IFireEvent Actions = Instance as IFireEvent;
                    if (Actions != null)
                        Actions.Run(Trigger.Init);

                    // returns the object instance
                    return Instance;
                }
            }
        #endregion
    }
}
